
#include "MaterialPhongVolume.h"
#include "HitRecord.h"
#include "Light.h"
#include "Primitive.h"
#include "Ray.h"
#include "RenderContext.h"
#include "Scene.h"
#include <float.h>
#include <fstream>
#include <iostream>
using namespace std;

void swap(short& data)
{
  //data = (data >> 8) | (data << 8);
  char* p = (char*)&data;
  char tmp = p[0];
  p[0] = p[1];
  p[1] = tmp;
}

MaterialPhongVolume::MaterialPhongVolume(const std::string& headername,
                                         const std::string& cmapname,
                                         const Point& lower, const Point& upper,
                                         double grid_stepsize, float maxopacity,
                                         float Kd, float Ka,
                                         const Color& phong_color,
                                         int phong_exponent)
  : cmap(cmapname), lower(lower), upper(upper), grid_stepsize(grid_stepsize),
    maxopacity(maxopacity), Kd(Kd), Ka(Ka), phong_color(phong_color),
    phong_exponent(phong_exponent)
{
  diag = upper-lower;

  ifstream hdr(headername.c_str());
  string volumename;
  hdr >> volumename;
  int nx, ny, nz;
  hdr >> nx >> ny >> nz;
  short offsetvalue;
  hdr >> offsetvalue;
  string fileendian;
  hdr >> fileendian;
  if(!hdr){
    cerr << "Error reading header: " << headername << '\n';
    exit(1);
  }
  if(fileendian != "BigEndian" && fileendian != "LittleEndian"){
    cerr << "Bad data endian: " << fileendian << " in " << headername << "\n";
    exit(1);
  }
  cerr << "Reading " << volumename << ": " << nx << 'x' << ny << 'x' << nz << '\n';
  data.resize(nx, ny, nz);
  cellsize = diag * Vector(1./(nx-1), 1./(ny-1), 1./(nz-1));
  ifstream in(volumename.c_str()); // 
  in.read(reinterpret_cast<char*>(&data(0,0,0)), nx*ny*nz*sizeof(short));
  if(!in){
    cerr << "Error reading data: " << volumename << '\n';
    if(in.eof())
    	cerr << "EOF bit set" << '\n';
    else if(in.fail())
    	cerr << "Fail bit set" << '\n';
    exit(1);
  }

  world_stepsize = cellsize.length()/pow(3, 1./3.) * grid_stepsize;
  cmap.rescale(world_stepsize);

  short tmp = 0x1234;
  string machineendian;
  if(*reinterpret_cast<char*>(&tmp) == 0x12)
    machineendian = "BigEndian";
  else
    machineendian = "LittleEndian";
  if(machineendian != fileendian){
    for(int i=0;i<nz;i++){
      for(int j=0;j<ny;j++){
        for(int k=0;k<nx;k++){
          swap(data(k,j,i));
          data(k,j,i) += offsetvalue;
        }
      }
    }
  } else {
    for(int i=0;i<nz;i++){
      for(int j=0;j<ny;j++){
        for(int k=0;k<nx;k++){
          data(k,j,i) += offsetvalue;
        }
      }
    }
  }
}

MaterialPhongVolume::~MaterialPhongVolume()
{
}


void MaterialPhongVolume::shade(Color& result, const RenderContext& context, const Ray& ray,
                                const HitRecord& hit, const Color& atten, int depth) const
{
  const Scene* scene = context.getScene();
  const vector<Light*>& lights = scene->getLights();
  Light*const* end = &lights[lights.size()];
  Point hitpos = ray.origin()+ray.direction()*hit.minT();

  Ray exitray(hitpos, ray.direction());
  HitRecord exithit(DBL_MAX);
  hit.getPrimitive()->intersect(exithit, context, exitray);
  if(!exithit.getPrimitive()){
    // Cannot find exit point, just return...
    result = Color(0,0,0);
    return;
  }
  double texit = hit.minT() + exithit.minT();

  const Object* world = scene->getObject();

  Color amblight = scene->getAmbient()*Ka;
  int it = Ceil(hit.minT()/world_stepsize);
  double t = it*world_stepsize;
  double accum_opacity = 0;
  Color accum_color(0, 0, 0);
  while(t < texit && accum_opacity < maxopacity){
    Point p(ray.origin()+t*ray.direction());
    Vector tp((p-lower)/cellsize);
    int ix = (int)tp.x();
    int iy = (int)tp.y();
    int iz = (int)tp.z();
    float fx = tp.x()-ix;
    float fy = tp.y()-iy;
    float fz = tp.z()-iz;
    float value = 
        data(ix,   iy,   iz  )*(1-fx)*(1-fy)*(1-fz)
      + data(ix+1, iy,   iz  )*fx    *(1-fy)*(1-fz)
      + data(ix,   iy+1, iz  )*(1-fx)*fy    *(1-fz)
      + data(ix+1, iy+1, iz  )*fx    *fy    *(1-fz)
      + data(ix,   iy,   iz+1)*(1-fx)*(1-fy)*fz
      + data(ix+1, iy,   iz+1)*fx    *(1-fy)*fz
      + data(ix,   iy+1, iz+1)*(1-fx)*fy    *fz
      + data(ix+1, iy+1, iz+1)*fx    *fy    *fz;
    float opacity;
    Color color;
    cmap.lookup(value, opacity, color);

    if(opacity > 0.0){
      Vector normal(data(ix+1, iy, iz)-data(ix-1, iy, iz),
                    data(ix, iy+1, iz)-data(ix, iy-1, iz),
                    data(ix, iy, iz+1)-data(ix, iy, iz-1));
      normal *= cellsize;
      if(normal.length2() > 0)
        normal.normalize();
      else {
        normal = ray.direction();
      }
      double costheta = -Dot(normal, ray.direction());
      if(costheta < 0){
        normal = -normal;
        costheta = -costheta;
      }
      Color difflight(amblight);
      Color speclight(0, 0, 0);
      Light*const* begin = &lights[0];
      while(begin != end){
        Color light_color;
        Vector light_direction;
        (*begin++)->getLight(light_color, light_direction, context, hitpos);
        double cosphi = Dot(normal, light_direction);
        if(cosphi > 0){
          // No shadows...
          difflight += light_color*(Kd*cosphi);
          Vector H = light_direction - ray.direction();
          H.normalize();
          double cos_alpha = Dot(H, normal);
          if(cos_alpha > 0){
            double phong_term = ipow(cos_alpha, phong_exponent);
            speclight += light_color*phong_term;
          }
        }
      }
      color = color*difflight + phong_color*speclight;

      accum_color += color * opacity * (1-accum_opacity);
      accum_opacity += opacity * (1-accum_opacity);
    }
    t += world_stepsize;
  }

  if(accum_opacity < maxopacity){
    // Trace a ray at the exit point...
    Point exitpoint(ray.origin()+ray.direction()*texit);
    Ray outsideray(exitpoint, ray.direction());
    Color exit_color;
    double T = 1-accum_opacity;
    scene->traceRay(exit_color, context, outsideray, Color(T, T, T), depth+1);
    accum_color += exit_color * (1-accum_opacity);
  }
  result = accum_color;
}